package com.cy.pj.sys.service.aspect;

import com.cy.pj.sys.common.annotation.RequiredLog;
import com.cy.pj.sys.pojo.SysLog;
import com.cy.pj.sys.service.SysLogService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Date;

/**
 * 在springAOP应用中，基于@Aspect注解描述的类型为切面类型
 * 此类中要封装切入点及通知方法的定义
 * 1）切入点：要切入扩展业务逻辑的一些目标方法集合（例如使用了特定注解描述的方法）
 * 2）通知：封装了扩展业务的一个方法（spring中也可以使用特定注解进行描述）
 */
@Order(1)
@Aspect
@Component
public class SysLogAspect {

    private static final Logger log = LoggerFactory.getLogger(SysLogAspect.class);

    /**
     * 基于@PointCut注解定义切入点，这里的@annotation为一种切入点表达式
     * 表示由RequiredLog注解描述的方法为一个切入点方法，我们要在这样的方法上添加扩展业务逻辑
     */
    @Pointcut("@annotation(com.cy.pj.sys.common.annotation.RequiredLog)")
    public void doLog(){}//此方法用于承载注解的定义

    /**
     * 定义一个通知方法，这里使用@Around进行描述，表示在此方法内部可以调用目标方法
     * 可以在目标方法执行之前或之后做一些拓展业务
     * @param jp 连接点，用于封装要执行的目标方法信息，ProceedingJoinPoint此类型的参数只能定义在@Around描述中
     * @return    这里的返回值一般为目标方法的执行结果
     * @throws Throwable
     */
    @Around("doLog()")
    public Object doAround(ProceedingJoinPoint jp)throws Throwable{
        Long t1 = System.currentTimeMillis();
        log.info("method start {}",t1);
        try {
            Object result = jp.proceed();//调用目标方法
            Long t2 = System.currentTimeMillis();
            log.info("method end {}",t2);
            saveLogInfo(jp,t2-t1,null);
            return result;
        }catch (Throwable e){
            Long t3 = System.currentTimeMillis();
            log.error("method error {}",t3);
            saveLogInfo(jp,t3-t1,e);
            throw e;
        }
    }
    @Autowired
    private SysLogService sysLogService;

    private void saveLogInfo(ProceedingJoinPoint jp ,Long time,Throwable e) throws NoSuchMethodException, JsonProcessingException {
        //获取用户行为日志
        //1.1获取登录用户信息
        String username = "nutony";//后续是系统的登录用户
        //1.2获取登录用户的ip地址
        String ip = "192.163.0.1";
        //1.3获取用户操作
        //获取方法所在类字节码对象(目标对象对应的字节码对象)
        Class<?> cls = jp.getTarget().getClass();
        //获取注解描述的方法对象（字节码对象，方法名，参数列表）
        Signature signature = jp.getSignature();
        MethodSignature ms = (MethodSignature)signature;
//        Method method1 = ms.getMethod(); //cglib
//        System.out.println("method"+method1);
        //1.4获取方法名
        Method targetMethod = cls.getDeclaredMethod(ms.getName(), ms.getParameterTypes());
        System.out.println("targetMethod:"+targetMethod);
        RequiredLog requiredLog = targetMethod.getAnnotation(RequiredLog.class);
        //1.5获取方法执行的实际参数
        Object[] args = jp.getArgs();//实际参数
        String params = new ObjectMapper().writeValueAsString(args);//转成json格式
        //获取RequiredLog注解
        String operation = requiredLog.operation();

        String method = cls.getName()+"."+targetMethod.getName();

        String param = params;

        Integer status = e==null?1:0;

        String error = e==null?"":e.getMessage();

        //封装用户行为日志
        SysLog sysLog = new SysLog();
        sysLog.setUsername(username);
        sysLog.setIp(ip);
        sysLog.setCreatedTime(new Date());
        sysLog.setOperation(operation);
        sysLog.setMethod(method);
        sysLog.setParams(param);
        sysLog.setStatus(status);
        sysLog.setError(error);
        sysLog.setTime(time);

        //将日志写入数据库
        log.info(new ObjectMapper().writeValueAsString(sysLog));
        sysLogService.saveLog(sysLog);

    }

}
